#include <map>
#include <Box2D/Box2D.h>
#include "GameCore/Core.h"
#include "Initializer.h"

using namespace cmn;

ReusableRotorDef* CreateRotorDef( const data::RotorDef& dataRotorDef );
ReusableBodyDef* CreateBodyDef( const data::Body& dataBody );

ReusableRotorDef* LoadReusableRotorDef( std::string filename );
ReusableBodyDef* LoadReusableBodyDef( std::string filename );

Rule DataRuleToRule( const data::Rule& dataRule );

Initializer::Initializer() :
	m_reusableBodyDefStore( LoadReusableBodyDef ),
	m_reusableRotorDefStore( LoadReusableRotorDef )
{}

Scene* Initializer::Initialize( std::string sceneFilename ) {
	return CreateScene( data::LoadScene( sceneFilename ) );
}

Scene* Initializer::CreateScene( const data::Scene& dataScene ) {
	Scene* scene = new Scene();

	std::map<std::string, int> creatureNameToId;

	for( IteratorPtr<data::Creature> dataCreatures( dataScene.GetCreatures() );
			!dataCreatures.IsEnd();
			dataCreatures.Next() )
	{
		const data::Creature dataCreature = dataCreatures.Current();
		int creatureId = InitializeCreature( scene, dataCreature );
		creatureNameToId[dataCreature.GetId()] = creatureId;
	}

	for( IteratorPtr<data::Player> players( dataScene.GetPlayers() );
			!players.IsEnd();
			players.Next() )
	{
		InitializePlayer( scene, creatureNameToId, players.Current() );
	}

	std::string mapFileName = dataScene.GetMapFileName();
	const data::Map dataMap = data::LoadMap( mapFileName );

	for( IteratorPtr<data::Obstacle> dataObstacles( dataMap.GetObstacles() );
			!dataObstacles.IsEnd();
			dataObstacles.Next() )
	{
		InitializeObstacle( scene, dataObstacles.Current() );
	}

	//scene->AddEntity( new Grid() );
	scene->SetCamera( CreateCamera( dataScene.GetCamera() ) );

	return scene;
}

Camera* Initializer::CreateCamera( const data::Camera& dataCamera ) {
	Camera* camera = new Camera(
		dataCamera.GetPosition(),
		dataCamera.GetVelocity(),
		dataCamera.GetScale() );

	core::Keyboard::AddListener( camera );
	return camera;
}

int Initializer::InitializeCreature( Scene* scene, const data::Creature& dataCreature ) {
	int creatureId = scene->CreateCreature();

	const data::CreatureDef dataCreatureDef = data::LoadCreatureDef( dataCreature.GetDefinitionFileName() );;

	Transform creaturePos = dataCreature.GetPosition();

	std::map<std::string, int> partNameToId;
	std::map<std::string, int> detailNameToId;

	for( IteratorPtr<data::Detail> dataDetails( dataCreatureDef.GetDetails() );
			!dataDetails.IsEnd();
			dataDetails.Next() )
	{
		const data::Detail dataDetail = dataDetails.Current();
		int detailId = InitializeDetail( scene, creatureId, creaturePos, dataDetail );
		detailNameToId[dataDetail.GetId()] = detailId;
		partNameToId[dataDetail.GetId()] = detailId;
	}

	for( IteratorPtr<data::Rotor> dataRotors( dataCreatureDef.GetRotors() );
			!dataRotors.IsEnd();
			dataRotors.Next() ) {
		const data::Rotor dataRotor = dataRotors.Current();
		int junctionId = InitializeJunction( scene, creatureId, creaturePos, detailNameToId, dataRotor );
		partNameToId[dataRotor.GetId()] = junctionId;
	}

	for( IteratorPtr<data::Link> dataLinks( dataCreatureDef.GetLinks() );
			!dataLinks.IsEnd();
			dataLinks.Next() )
	{
		const data::Link dataLink = dataLinks.Current();
		int partAId = partNameToId[ dataLink.GetPartAId() ];
		int partBId = partNameToId[ dataLink.GetPartBId() ];
		scene->AddLink( creatureId, partAId, partBId );
	}
	return creatureId;
}

int Initializer::InitializeObstacle( Scene* scene, const data::Obstacle& dataObstacle ) {
	const std::string modelFileName = dataObstacle.GetModelFileName();
	const data::Model dataModel = data::LoadModel( modelFileName );

	const data::Texture dataTexture = dataModel.GetTexture();
	const core::Texture* tex = core::Renderer::GetTextureStore().Get( dataTexture.GetFileName() );
	TextureInfo* texInfo = new TextureInfo( tex, dataTexture.GetPosition(), dataTexture.GetWidth(), dataTexture.GetHeight() );

	ReusableBodyDef* bodyDef = m_reusableBodyDefStore.Get( dataModel.GetBodyFileName() );

	return scene->CreateObstacle( dataObstacle.GetPosition(), bodyDef, texInfo );
}

int Initializer::InitializeDetail( Scene* scene, int creatureId, const Transform& creaturePos, const data::Detail& dataDetail ) {
	const std::string modelFileName = dataDetail.GetModelFileName();
	const data::Model dataModel = data::LoadModel( modelFileName );

	Transform detailPos = creaturePos * dataDetail.GetPosition();

	const data::Texture dataTexture = dataModel.GetTexture();
	const core::Texture* tex = core::Renderer::GetTextureStore().Get( dataTexture.GetFileName() );
	TextureInfo* texInfo = new TextureInfo( tex, dataTexture.GetPosition(), dataTexture.GetWidth(), dataTexture.GetHeight() );

	ReusableBodyDef* bodyDef = m_reusableBodyDefStore.Get( dataModel.GetBodyFileName() );

	int detailId = scene->CreateDetail( creatureId, detailPos, Transform::Zero(), bodyDef, texInfo );

	for( IteratorPtr<data::Rule> dataRules( dataDetail.GetRules() );
			!dataRules.IsEnd();
			dataRules.Next() )
	{
		scene->AddRule( detailId, DataRuleToRule( dataRules.Current() ) );
	}
	return detailId;
}

int Initializer::InitializeJunction( Scene* scene, int creatureId, const Transform& creaturePos, std::map<std::string, int>& detailNameToId, const data::Rotor& dataRotor ) {

	Vec2 pos = creaturePos * dataRotor.GetPosition();

	int detailAId = detailNameToId[dataRotor.GetDetailAId()];
	int detailBId = detailNameToId[dataRotor.GetDetailBId()];

	ReusableRotorDef* reusableRotorDef = m_reusableRotorDefStore.Get( dataRotor.GetDefinitionFileName() );

	int junctionId = scene->CreateJunction( creatureId, pos, detailAId, detailBId, reusableRotorDef );

	for( IteratorPtr<data::Rule> dataRules( dataRotor.GetRules() );
			!dataRules.IsEnd();
			dataRules.Next() )
	{
		scene->AddRule( junctionId, DataRuleToRule( dataRules.Current() ) );
	}
	return junctionId;
}

int Initializer::InitializePlayer( Scene* scene, std::map<std::string, int>& creatureNameToId, const data::Player& dataPlayer ) {
	int creatureId = creatureNameToId[dataPlayer.GetCreatureId()];
	switch( dataPlayer.GetType() ) {
		case data::PlayerType::EmptyPlayer:
			return scene->CreateBotPlayer( creatureId );
		case data::PlayerType::HumanPlayer:
			return scene->CreateHumanPlayer( creatureId );
		case data::PlayerType::Unknown:
			throw NotFoundException( "Unknown player type" );
		default:
			throw NotFoundException( "Unknown player type" );
	}
}

ReusableRotorDef* LoadReusableRotorDef( std::string filename ) {
	const data::RotorDef dataRotorDef = data::LoadRotorDef( filename );
	ReusableRotorDef* rotorDef = CreateRotorDef( dataRotorDef );
	return rotorDef;
}

ReusableBodyDef* LoadReusableBodyDef( std::string filename ) {
	const data::Body dataBody = data::LoadBody( filename );
	ReusableBodyDef* bodyDef = CreateBodyDef( dataBody );
	return bodyDef;
}

ReusableRotorDef* CreateRotorDef( const data::RotorDef& dataRotorDef ) {
	return new ReusableRotorDef( false, false, 0.0f, 0.0f, true, dataRotorDef.GetTorque(), 0.0f );
}

ReusableBodyDef* CreateBodyDef( const data::Body& dataBody ) {
	ReusableBodyDef* bodyDef =
		new ReusableBodyDef(
		dataBody.GetMass(),
		dataBody.GetInertia(),
		dataBody.GetFriction(),
		dataBody.GetRestitution() );

	for( IteratorPtr<Circle> dataCircles( dataBody.GetCircles() ); !dataCircles.IsEnd(); dataCircles.Next() ) {
		const Circle circle = dataCircles.Current();
		bodyDef->AddCircle( circle );
	}

	for( IteratorPtr<data::PolygonShape> dataPolygons( dataBody.GetPolygons() );
			!dataPolygons.IsEnd();
			dataPolygons.Next() )
	{
		const data::PolygonShape dataPolygon = dataPolygons.Current();
		Transform polygonPos = dataPolygon.GetPosition();
		Polygon polygonRelated = dataPolygon.GetPolygon();

		Vector<Vec2> polygonVertices;
		polygonRelated.GetVertices( polygonVertices );

		Polygon polygonAbsolute;
		for( int i = 0; i < polygonVertices.GetLength(); ++i ) {
			polygonAbsolute.AddVertex( polygonPos * polygonVertices[i] );
		}

		bodyDef->AddPolygon( polygonAbsolute );
	}
	return bodyDef;
}

CommandType DataCommandTypeToCommandType( const data::CommandType& dataCommandType ) {
	switch( dataCommandType ) {
		case data::CommandType::Press:
			return CommandType::Press;
		case data::CommandType::Release:
			return CommandType::Release;
		case data::CommandType::Move:
			return CommandType::Move;
		case data::CommandType::Send:
			return CommandType::Send;
		case data::CommandType::UnknownCommandType:
			return CommandType::UnknownCommandType;
		default:
			return CommandType::UnknownCommandType;
	}
}

Command DataCommandToCommand( const data::Command& dataCommand ) {
	return Command( DataCommandTypeToCommandType( dataCommand.GetType() ), dataCommand.GetKey() );
}

Action DataActionToAction( const data::Action& dataAction ) {
	switch( dataAction ) {
		case data::Action::StartLeftRotation:
			return Action::StartLeftRotation;
		case data::Action::StopLeftRotation:
			return Action::StopLeftRotation;
		case data::Action::StartRightRotation:
			return Action::StartRightRotation;
		case data::Action::StopRightRotation:
			return Action::StopRightRotation;
		case data::Action::Grab:
			return Action::Grab;
		case data::Action::WatchCursor:
			return Action::WatchCursor;
		case data::Action::UnknownAction:
			return Action::UnknownAction;
		default:
			return Action::UnknownAction;
	}
}

Rule DataRuleToRule( const data::Rule& dataRule ) {
	return Rule(
			   DataCommandToCommand( dataRule.GetCommand() ),
			   DataActionToAction( dataRule.GetAction() ) );
}
